import base64
import json
import pickle

from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
from django.http import JsonResponse, HttpResponseBadRequest
from django.shortcuts import render, get_object_or_404
from django.views import View
from django_redis import get_redis_connection
from haystack.views import SearchView

from apps.contents.models import ContentCategory
from apps.goods.models import GoodsChannel, SKU, GoodsCategory, SKUSpecification

from utils.goods import get_breadcrumb, get_categories, get_specs
from utils.tools import tools
from utils.views import LoginRequiredJSONMixin

# 1. 展示首页频道和分类数据的类视图
class ChannelsView(View):
    def get(self, request):
        # 1. 查出频道数据(类别和频道组的关联表)
        goods_channels = GoodsChannel.objects.all().order_by('group', 'sequence')

        # 2. 把数据转换成前端想要的格式
        response_dict = {}
        for channel in goods_channels:
            # 0. 打印输出频道组的id  和频道关联到的类别名称
            print(channel.group.id, channel.category.name)

            # 1. 得到频道组对应的商品类别
            category = channel.category

            # 2. 获取频道组大字典： 有就用已存在的，没有就构建一个
            group_dict = response_dict.get(channel.group.id)
            if not group_dict:
                group_dict = {'channels': [], 'sub_cats': []}
                response_dict[channel.group.id] = group_dict

            # 3. 往频道组的大字典中的channels中添加当前频道数据
            group_dict['channels'].append({"id": channel.id, "name": category.name, "url": channel.url})

            # 4. 得到一级类别下的所有二级类别
            sub_cats = category.subs.all()
            for sub_cat in sub_cats:
                # 4.1 准备二级类别字典
                sub_cat_dict = {'id': sub_cat.id, 'name': sub_cat.name, 'sub_cats': []}

                # 4.2 给二级字典类别字典的sub_cats添加三级类别的字典数据
                # 得到二级类别下的三级类别
                sub_thrid_cats = sub_cat.subs.all()
                for sub_thrid_cat in sub_thrid_cats:
                    sub_cat_dict['sub_cats'].append({
                        'id': sub_thrid_cat.id,
                        'name': sub_thrid_cat.name}
                    )
                # 4.3 把二级类别字典添加到组字典的 sub_cats列表中
                group_dict['sub_cats'].append(sub_cat_dict)

        # 3. 返回结果
        return JsonResponse(response_dict)


# 3. 商品列表的类视图
class ListView(View):
    def get(self, request, category_id):
        # 1. 获取参数
        page = request.GET.get('page')
        page_size = request.GET.get('page_size')
        ordering = request.GET.get('ordering')

        # 2. 校验参数
        if not all([page, page_size, ordering]):
            return JsonResponse({'code': 400, 'errmsg': '参数不能为空'})
        if ordering not in [
            'create_time', 'price', 'sales', '-create_time', '-price', '-sales']:
            return JsonResponse({'code': 400, 'errmsg': '排序参数错误'})

        # 3. 往数据库中查出商品数据
        skus = SKU.objects.filter(
            category__id=category_id).order_by(ordering)

        # 4. 并进行分页的处理
        # 4.1 创建分页器
        try:
            paginator = Paginator(skus, page_size)
        except:
            # 如果page_size不是一个正整数，则默认每页10条记录
            paginator = Paginator(skus, 10)

        # 4.2 得到特定页的数据
        try:
            page_skus = paginator.page(page)
        except PageNotAnInteger:
            # 如果页码不是一个整数，默认就获取第一页的数据
            page_skus = paginator.page(1)
        except EmptyPage:
            # 如果页码超出了总页数，则返回最后一页数据
            page_skus = paginator.page(paginator.num_pages)

        # 5. 把数据转换成前端想要的格式
        list = []
        for sku in page_skus:
            list.append(sku.to_dict())

        # 6. 返回结果
        return JsonResponse({
            'code': 0,
            'errmsg': 'ok',
            'list': list,
            'breadcrumb': get_breadcrumb(category_id),
            'count': paginator.num_pages
        })


# 3. 热销商品的类视图
class HotGoodsView(View):
    def get(self, request, category_id):
        # 1. 利用category_id去数据库把该类别的商品按照销量倒序排序，并取出前3条
        skus = SKU.objects.filter(category__id=category_id).order_by('-sales')[:3]

        # 2. 把数据转换成前端想要的格式
        hot_skus = []
        for sku in skus:
            hot_skus.append(sku.to_dict())

        # 3. 返回结果
        return JsonResponse({'code': 0, 'errmsg': 'ok', 'hot_skus':hot_skus})


# 4. 搜索的视图
class MySearchView(SearchView):
    def create_response(self):
        context = self.get_context()
        sku_objects_page = context['page']

        res_list = []
        for sku_object in sku_objects_page:
            sku = sku_object.object
            sku_dict = sku.to_dict()
            sku_dict['searchkey'] =context['query']
            sku_dict['page_size'] = context['paginator'].per_page
            sku_dict['count'] = context['paginator'].num_pages
            res_list.append(sku_dict)

        return JsonResponse(res_list, safe=False)


# 5. 商品详情的类视图（前后端不分离）
class DetailView(View):
    def get(self, request, sku_id):
        # 1. 利用sku_id把sku查出来
        sku = get_object_or_404(SKU, id=sku_id)

        # 2. 查出当前spu的规格数据
        specs = get_specs(sku)

        # 3. 构建上下文参数
        context = {
            'sku': sku,
            'categories': get_categories(),
            'breadcrumb':  get_breadcrumb(sku.category.id),
            'specs': specs
        }
        # 4. 返回结果
        return render(request, 'detail.html', context)


# 6. 首页的视图函数
class IndexView(View):
    def get(self, request):
        # 1. 准备商品类别数据
        categories = get_categories()

        # 2. 准备首页的广告数据
        contents = {}
        # 2.1 查出所有广告类别
        content_categories = ContentCategory.objects.all()
        # 2.2 遍历类别，查出类别下的广告
        for content_category in content_categories:
            contents[content_category.key] = content_category.content_set.order_by('sequence')

        # 3. 构建上下文字典
        context = {
            'categories': categories,
            'contents': contents
        }

        # 4. 返回渲染的结果
        return render(request, 'index.html', context)


# 7. 用户浏览记录
class UserBrowseHistory(LoginRequiredJSONMixin,View):
    # post: 添加
    def post(self, request):
        # 1. 接收参数
        req_str = request.body.decode()
        req_dict = json.loads(req_str)
        sku_id = req_dict.get('sku_id')
        # print(sku_id)

        # 2. 校验参数
        try:
            sku = SKU.objects.get(id=sku_id)
        except SKU.DoesNotExist:
            return JsonResponse({'code': 400, 'errmsg': 'sku_id不合法'})

        # 3. 添加浏览记录到redsi中
        # 3.1 得到操作redis数据的一个连接和管道
        conn = get_redis_connection('history')
        pl = conn.pipeline()

        # 3.2 构建读和存数据的key
        key = 'history_{}'.format(request.user.id)
        # 历史浏览记录：[1, 2, 3, 5, 6]
        # 当前浏览的sku_id: 4
        # 3.3 从list中删除可能存在sku_id   [1, 2, 3, 5, 6]
        pl.lrem(key, 0, sku_id)
        # 3.4 把sku_id添加到list中    [4, 1, 2, 3, 5, 6]
        pl.lpush(key, sku_id)
        # 3.5 从list中截取保留索引为0到4数据 （共五条）
        pl.ltrim(key, 0, 4)
        # 3.6 执行管道中的所有命令
        pl.execute()
        # 4. 返回结果
        return JsonResponse({'code': 400, 'errmsg': 'ok'})

    # get: 获取浏览记录列表
    def get(self, request):
        # 1. 去reids中查出用户最近浏览的商品的sku_id的列表
        conn = get_redis_connection('history')
        key = 'history_{}'.format(request.user.id)
        sku_ids = conn.lrange(key, 0, -1)  # [b'16', b'15', b'14']
        sku_ids = [sku_id.decode() for sku_id in sku_ids ] # ['16', '15', '14']

        skus = []
        for sku_id in sku_ids:
            # 2. 利用这个id的去数据库中把商品查出来
            try:
                sku = SKU.objects.get(id=sku_id)
            except SKU.DoesNotExist:
                continue

            # 3. 转换成前端想要的格式
            skus.append(sku.to_dict())
        # 4. 返回结果
        return JsonResponse({'code': 0, 'errmsg': 'ok', 'skus':skus})


# 8. 购物车的类视图
class CartsView(View):
    # 添加购物车
    def post(self, request):
        user = request.user
        # 1. 接收参数
        req_str = request.body.decode()
        req_dict = json.loads(req_str)
        sku_id = req_dict.get('sku_id')
        count = req_dict.get('count')
        selected = req_dict.get('selected')

        # 2. 校验参数
        # 判断参数是否齐全
        if not all([sku_id, count]):
            return JsonResponse({'code': 400, 'errmsg': '缺少必传参数'})
        # 判断sku_id是否存在
        try:
            SKU.objects.get(id=sku_id)
        except SKU.DoesNotExist:
            return JsonResponse({'code': 400, 'errmsg': '商品不存在'})

        # 判断count是否为数字
        try:
            count = int(count)
        except Exception:
            return JsonResponse({'code': 400, 'errmsg': '参数count有误'})

        # 判断selected是否为bool值
        if selected:
            if not isinstance(selected, bool):
                return JsonResponse({'code': 400, 'errmsg': '参数selected有误'})

        # 3. 判断用户是不是已登录
        # 3.1 已登录操作redis存储购物车数据
        if user.is_authenticated:
            # 1. 得到一个redis的连接
            conn = get_redis_connection('carts')
            # 2. 基于连接得到一个管道
            pl = conn.pipeline()
            # 3. 往hash中放入商品的数量
            #   sku_id 1 count 1       {1:4, 2:4}
            pl.hincrby('carts_{}'.format(user.id), sku_id, count)
            # 4. 如果当前是勾选状态则把当前的sku_id放到集合set中
            if selected:
                pl.sadd('selected_{}'.format(user.id), sku_id)
            # 5. 执行管道中的命令
            pl.execute()
            # 6. 返回结果
            return JsonResponse({'code': 0, 'errmsg': 'ok'})

        # 3.2 未登录操作cookie存储购物车数据
        else:
            # 1. 读取出cookie中的购物车数据（加密字符串）
            carts = request.COOKIES.get('carts')

            # 2. 把存储在cookie中的数据（加密字符串）转成字典对象
            if carts:
                # 如果有则解密
                carts = tools.sloads(carts)
            else:
                # 如果没有则创建新的 carts字典
                carts = {}

            # 3. 操作字典，添加购物车数据
            sku_dict = carts.get(sku_id)
            if sku_dict:
                sku_dict['count'] += count
            else:
                sku_dict = {
                    'count': count,
                    'selected': selected
                }
                carts[sku_id] = sku_dict

            # 4. 定义要返回的结果
            response = JsonResponse({'code': 0, 'errmsg': 'ok'})
            # 5. 把修改后的字典转成加密字符串，把数据存在cookie中
            response.set_cookie(
                'carts', tools.sdumps(carts), 7*24*3600)
            # 6. 返回结果
            return response

    # 浏览购物车
    def get(self, request):
        user = request.user
        # 1. 判断用户有没有登录
        # 2. 如果用户为登录状态去redis中把数据取出来
        if user.is_authenticated:
            # 0. 得到一个redis的连接
            conn = get_redis_connection('carts')
            # 1. 查hash中的数据
            hashr = conn.hgetall('carts_{}'.format(user.id))
            print(hashr)  # {b'16': b'2', b'15': b'2'}
            # 2. 查set中的数据
            setr = conn.smembers('selected_{}'.format(user.id))
            print(setr)   # {b'16',  b'15'}
            # 3. 把数据转成字典的形式
            carts = {}
            for (sku_id_bytes, count) in hashr.items():
                sku_id = sku_id_bytes.decode()
                count = int(count.decode())
                carts[sku_id] = {
                    'count':count,
                    'selected':sku_id_bytes in setr
                }
            print(carts)
        # 3. 如果用户没有登录则去cookie中把数据取出来
        else:
            # 1. 取出cookie中的数据
            carts_str = request.COOKIES.get('carts')
            # 2. 如果有则进行解密
            if carts_str:
                carts = tools.sloads(carts_str)
            # 3. 如果没有则创建一个新的
            else:
                carts = {}
            print(carts)

        # 4. 把数据转换成前端想要的格式
        cart_skus = []
        for (sku_id, cart_dict) in carts.items():
            try:
                sku = SKU.objects.get(id=sku_id)
            except SKU.DoesNotExist:
                continue
            # 转成字典
            sku_dict = sku.to_dict()
            # 添加购物车数量
            sku_dict['count'] = cart_dict['count']
            # 添加是否勾选中
            sku_dict['selected'] = cart_dict['selected']
            # 添加总价
            sku_dict['amount'] = sku_dict['count'] * sku.price
            cart_skus.append(sku_dict)

        # 5. 返回结果
        return JsonResponse({'code': 0, 'errmsg': 'ok', 'cart_skus': cart_skus})

    # 修改购物车
    def put(self, request):
        user = request.user
        # 1. 接收参数
        req_str = request.body.decode()
        req_dict = json.loads(req_str)
        sku_id = req_dict.get('sku_id')
        count = req_dict.get('count')
        selected = req_dict.get('selected')

        # 2. 校验参数
        # 判断参数是否齐全
        if not all([sku_id, count]):
            return JsonResponse({'code': 400, 'errmsg': '缺少必传参数'})
        # 判断sku_id是否存在
        try:
            sku = SKU.objects.get(id=sku_id)
        except SKU.DoesNotExist:
            return JsonResponse({'code': 400, 'errmsg': '商品不存在'})

        # 判断count是否为数字
        try:
            count = int(count)
        except Exception:
            return JsonResponse({'code': 400, 'errmsg': '参数count有误'})

        # 判断selected是否为bool值
        if selected:
            if not isinstance(selected, bool):
                return JsonResponse({'code': 400, 'errmsg': '参数selected有误'})
        # 3. 判断用户是不是已登录
        # 4. 已登录则修改redis中的购物车数据
        if user.is_authenticated:
            # 1. 得到一个redis的连接
            conn =  get_redis_connection('carts')
            # 2. 得到一个redis管道
            pl = conn.pipeline()
            # 3. 修改hash中的数据 sku_id: 1000  count: 3
            pl.hset('carts_{}'.format(user.id), sku_id, count)
            # 4. 修改set中的数据
            if selected:
                pl.sadd('selected_{}'.format(user.id), sku_id)
            else:
                pl.srem('selected_{}'.format(user.id), sku_id)
            # 5. 执行命令
            pl.execute()

            # 6. 构建返回的字典
            sku_dict = sku.to_dict()
            # 添加购物车数量
            sku_dict['count'] = count
            # 添加是否勾选中
            sku_dict['selected'] = selected
            # 添加总价
            sku_dict['amount'] = count * sku.price

            # 7. 返回结果
            return JsonResponse({
                'code': 0, 'errmsg': '修改购物车成功', 'cart_sku': sku_dict})

        # 5. 未登录则修改cookie中的数据
        else:
            # 1. 取出cookie中的数据
            carts_str = request.COOKIES.get('carts')
            # 2. 如果有则进行解密
            if carts_str:
                carts = tools.sloads(carts_str)
            # 3. 如果没有则创建一个新的
            else:
                carts = {}
            # 4. 修改购物车字典中的数据
            carts[sku_id] = {
                'count': count,
                'selected': selected
            }

            # 5. 构建一个响应对象
            sku_dict = sku.to_dict()
            # 添加购物车数量
            sku_dict['count'] = count
            # 添加是否勾选中
            sku_dict['selected'] = selected
            # 添加总价
            sku_dict['amount'] = count * sku.price
            resp = JsonResponse({
                'code': 0, 'errmsg': '修改购物车成功', 'cart_sku': sku_dict})

            # 6. 把数据存到cookie中
            resp.set_cookie('carts', tools.sdumps(carts), 7*24*3600)
            # 7. 返回结果
            return resp

    # 删除购物车
    def delete(self, request):
        user = request.user
        # 1. 接收sku_id的参数
        req_str = request.body.decode()
        req_dict = json.loads(req_str)
        sku_id = req_dict.get('sku_id')

        # 2. 校验
        try:
            SKU.objects.get(id=sku_id)
        except SKU.DoesNotExist:
            return JsonResponse({'code': 400, 'errmsg': 'sku_id错误'})

        # 3. 判断用户有没有登录
        # 4. 如果已登录则修改redis中的数据
        if user.is_authenticated:
            # 1. 得到一个redis的连接对象
            conn = get_redis_connection('carts')
            # 2. 得到一个redis的管道
            pl = conn.pipeline()
            # 3. 修改hash中的数据
            pl.hdel('carts_{}'.format(user.id), sku_id)
            # 4. 修改set中的数据
            pl.srem('selected_{}'.format(user.id), sku_id)
            # 5. 执行管道中的命令
            pl.execute()
            # 6. 返回结果
            return JsonResponse({'code': 0, 'errmsg': 'ok'})
        # 5. 如果没有登录则修改cookie中的数据
        else:
            # 1. 定义一个公共的响应对象
            response = JsonResponse({'code': 0, 'errmsg': 'ok'})
            # 2. 获取cookie中把购物车的字符串
            carts_str =  request.COOKIES.get('carts')
            # 3. 如果没有该字符串则响应删除成功
            if not carts_str:
                return response
            # 4. 如果有则对字符进行解析得到购物车字典
            carts = tools.sloads(carts_str)
            # 5. 删除购物车字典中指定sku_id的商品
            try:
                del carts[sku_id]
            except KeyError:
                pass
            # 6. 把修改后的字典加密成字符串，放到cookie中
            response.set_cookie(
                'carts', tools.sdumps(carts), 7 * 24 * 3600)
            # 7. 返回结果
            return response

# 9. 全选和取消全选的类视图
class CartsSelectAllView(View):
    def put(self, request):
        user = request.user
        # 1. 接收参数
        req_str = request.body.decode()
        req_dict = json.loads(req_str)
        selected = req_dict.get('selected')
        # 2. 判断用户有没有登录
        # 3. 如果有登录就修改redis中的数据
        if user.is_authenticated:
            # 1. 得到一个redis的连接
            conn =  get_redis_connection('carts')
            # 2. 判断是不是选中
            # 3. 如果时选中（全选），则需要把hash中sku_id都放到set中去
            if selected:
                # 3.1 读取出hash中的sku_id，放到hashkeys的列表中
                hashkeys = conn.hkeys('carts_{}'.format(user.id)) # [b'14', b'12']
                hashkeys = [hashkey.decode() for hashkey in hashkeys] # ['14', '12']
                # 3.2 把列表中sku_id放到集合中
                conn.sadd('selected_{}'.format(user.id), *hashkeys)
            # 4. 如果没有选中（取消全选），则把set给删掉
            else:
                conn.delete('selected_{}'.format(user.id))
            # 5. 返回结果
            return JsonResponse({'code': 0, 'errmsg': 'ok'})

        # 4. 如果没有登录就修改cookie中的数据
        else:
            # 0. 定义一个通用的响应成功的对象
            response = JsonResponse({'code': 0, 'errmsg': 'ok'})
            # 1. 查出cookie中购物车字符串
            carts_str = request.COOKIES.get('carts')
            # 2. 如果没有则直接响应成功
            if not carts_str:
                return response
            # 3. 如果有则把字符串解密成购物车字典
            carts = tools.sloads(carts_str)
            # 4. 迭代得到每个商品的小字典，修改勾选状态
            for cart_dict in  carts.values():
                cart_dict['selected'] = bool(selected)
            # 5. 把修改后的字典加密成字符串，存到cookie中
            response.set_cookie('carts', tools.sdumps(carts), 7 * 24 * 3600)
            # 6. 返回结果
            return response
